/*!
 * OpenSocial Dashboard
 * http://code.google.com/p/opensocial-dashboard/
 *
 * Copyright (c) 2009 stomita (shinichi.tomita@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

/**
 *
 *
 */
var SocialDashboard = new function() {

/**
 *
 */
function extend(superclass, properties) {
  var f = function() {
    superclass.apply(this, arguments);
    this.init.apply(this, arguments);
  }
  var subclass = function(){};
  subclass.prototype = superclass.prototype;
  f.prototype = new subclass;
  $.extend(f.prototype, properties);
  return f;
}

/**
 *
 *
 */
function deepCopy(obj) {
  return gadgets.json.parse(gadgets.json.stringify(obj));
}


/**
 *
 */
var Dashboard = function(config, container) { 
  var dashboard = this;

  this.config = config;

  this.el = $('<div class="dashboard" />')
    .appendTo(container)
    .data('dashboard', this)
    .height($(document).height())
    .droppable({
      scope : 'gadget',
      drop : function(e, ui) {
        var gdconfig = ui.draggable.data('config');
        var position = $(this).position();
        var x = e.pageX - position.left - 100;
        var y = e.pageY - position.top - 100;
        dashboard.addGadget($.extend({ x : Math.max(0, x), y : Math.max(0, y) }, gdconfig));
      }
    })

  this.prepareScreens(config);
  this.renderScreenNav();
  this.renderProfile(config);
  this.switchToDefault(); 
  this.dirty(false);

  $(window).resize(function(){
    var h = $(window).height();
    $(document.body).children('.root').height(h);
    dashboard.el.height(h);
  });

  gadgets.rpc.register('set_pref', function(id, name, value) {
    var iframe = $('#'+this.f); // internal code reference from rpc.js, might be instable.
    var gadget = iframe.parents('.floating-gadget:first').data('gadget');
    if (!gadget) return;
    gadget.pref(name, value);
    dashboard.dirty(true);
  });

  gadgets.rpc.register('set_title', function(title) {
    var iframe = $('#'+this.f); // internal code reference from rpc.js, might be instable.
    var gadget = iframe.parents('.floating-gadget:first').data('gadget');
    if (!gadget) return;
    gadget.title(title);
  });

}

Dashboard.LABEL_SETTING_LINK = 'Setting';
Dashboard.LABEL_LOGOUT_LINK = 'Logout';

Dashboard.prototype = {
  /**
   *
   */
  renderProfile : function() {
    var dashboard = this;
    var profileEl = $('<div class="profile" />')
      .append('<div class="l"><div class="c"></div></div>')
      .append('<div class="bl"><div class="bc"></div></div>')
      .appendTo(this.el)

    if ($.anonymous) {
      $('<div class="login" />')
        .appendTo(profileEl.find('.c'))
        .signIn(function() { SocialDashboard.refresh(); })
    } else {
      $.ajax({
        url : '/people/@viewer/@self',
        dataType : 'data'
      })
      .next(function(data) {
        var viewer = data[0];
        profileEl.find('.c')
         .append($('<img class="photo"/>').attr('src', viewer.thumbnailUrl))
         .append($('<span class="name" />').text(viewer.displayName))
         .append('<br />')
         .append($('<a href="javascript:false" class="setting" />')
                 .text(Dashboard.LABEL_SETTING_LINK)
                 .click(function() { 
                   dashboard.showSettingDialog();
                   return false;
                 }))
         .append($('<span class="separator" />').text('|'))
         .append($('<a href="javascript:false" class="logout" />')
                 .text(Dashboard.LABEL_LOGOUT_LINK)
                 .click(function() { 
                   $.signOut(function() { SocialDashboard.refresh() });
                   return false;
                 }))
         .append('<div class="clearfix" />')
      })
    }
  }
  ,

  /**
   *
   */
  prepareScreens : function(config) {
    for (var i=0, len=config.screens.length; i<len; i++) {
      var scrconfig = config.screens[i];
      $('<div class="screen" id="screen-'+(i+1)+'"/>')
        .append(
          $('<div class="wallpaper" />').css({
            backgroundColor : scrconfig.bgcolor || 'transparent',
            backgroundImage : scrconfig.bgimage ? 'url('+scrconfig.bgimage+')' : 'none',
            backgroundRepeat : 'repeat',
            backgroundSize : '100%',
            opacity : typeof scrconfig.opacity == 'number' ? scrconfig.opacity : 1
          })
        )
        .hide()
        .appendTo(this.el)
    }
  }
  ,

  /**
   *
   */
  renderScreenNav : function() {
    var dashboard = this;
    dashboard.el 
      .append($('<div class="nav prev" />').html('&laquo;'))
      .append($('<div class="nav next" />').html('&raquo;'))
      .find('.nav')
        .css({
          top : Math.floor((dashboard.el.height() - 250)/2),
          opacity : .1
        })
        .mouseover(function() { 
          $(this).css('opacity', .5)
        })
        .mouseout(function() {
          $(this).css('opacity', .1)
        })
        .click(function() {
          var offset = $(this).hasClass('prev') ? -1 : 1;
          dashboard.switchTo(dashboard.targetIdx+offset);
        });
  }
  ,

  /**
   *
   */
  switchToDefault : function() {
    var locationUrl = environment.locationUrl;
    this.targetIdx = 0;
    if (locationUrl) {
      var matched;
      for (var i=0, len=this.config.screens.length; i<len; i++) {
        var scrconfig = this.config.screens[i];
        if (scrconfig.urlpattern) {
          var urlpattern = 
            '^'+scrconfig.urlpattern.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
          if (new RegExp(urlpattern).test(locationUrl)) {
            matched = true;
            this.targetIdx = i;
            break;
          }
        }
        if (matched) break;
      }
    }
    this.switchTo(this.targetIdx)
  }
  ,

  /**
   *
   */
  switchTo : function(idx) {
    var num = this.config.screens.length;
    while (idx<0) idx += num;
    idx = idx % num;
    var scrconfig = this.config.screens[idx];
    for (var i=0, len=this.config.screens.length; i<len; i++) {
      var cfg = this.config.screens[i];
      $('#screen-'+(i+1))[i == idx ? "show" : "hide"]();
    }
    this.targetIdx = idx;
    if (!scrconfig.rendered) {
      scrconfig.rendered = true;
      this.renderGadgets(scrconfig);
    }
  }
  ,

  /**
   *
   */
  renderGadgets : function(scrconfig) {
    var dashboard = this;
    var gadget;
    $.each(scrconfig.gadgets || [], function(i, gdconfig) {
      gadget = dashboard.addGadget(gdconfig);
    });
    if (gadget) gadget.focus();
  }
  ,

  /**
   *
   */
  currentScreen : function() {
    return this.el.find('#screen-'+(this.targetIdx+1));
  }
  ,

  /**
   *
   */
  addGadget : function(gdconfig, focus) {
    this.dirty(true);
    return new Gadget(gdconfig, this, focus);
  }
  ,

  /**
   *
   */
  refreshBackground : function() {
    var scrconfig = this.config.screens[this.targetIdx];
    this.currentScreen().find('> .wallpaper').css({
      backgroundColor : scrconfig.bgcolor || 'transparent',
      backgroundImage : scrconfig.bgimage ? 'url('+scrconfig.bgimage+')' : 'none',
      opacity : typeof scrconfig.opacity == 'number' ? scrconfig.opacity : 1
    })
  }
  ,

  /**
   *
   */
  persist : function() {
    var dashboard = this;
    var gadgets = [];
    this.currentScreen().find('.floating-gadget').each(function() {
      var gadget = $(this).data('gadget');
      gadgets.push([ $(this).css('zIndex'), gadget.config() ]);
    })
    gadgets = $.map(gadgets.sort(function(g1, g2) {
      var z1 = g1[0], z2 = g2[0];
      return z1 > z2 ? 1 : z1 < z2 ? -1 : 0;
    }), function(g) { return g[1] });
    dashboard.config.screens[this.targetIdx].gadgets = gadgets;
  }
  ,


  /**
   *
   */
  save : function() {
    this.persist();

    var dbconfig = $.extend({}, this.config);
    dbconfig.screens = $.map(dbconfig.screens, function(scrconfig) {
      scrconfig = $.extend({}, scrconfig);
      delete scrconfig.rendered;
      return scrconfig;
    });

    $.ajax({
      type: 'post',
      url: '/appdata/@viewer/@self',
      data: {
        dashboard : dbconfig
      },
      dataType: 'data'
    });

    this.dirty(false);
    return this;
  }
  ,

  /**
   *
   */
  dirty : function(dirty) { 
    if (typeof dirty !== 'undefined') {
      this._dirty = dirty; 
      return this;
    } else {
      return this._dirty;
    }
  }
  ,

  /**
   *
   */
  showSettingDialog : function() {
    new SettingDialog({ width : 400, height : 250, dashboard : this }).show();
  }

}


/**
 *
 *
 */
var Gadget = function(config, dashboard, focus) { 
  var gadget = this;
  this.initConfig = config;

  var id = 'floating-gadget-'+(Gadget.count++);
  gadget.el = $('<div class="floating-gadget" id="'+id+'"></div>')
    .append($('<div class="main" />')
      .addClass(config.color || '')
      .append('<div class="tl"><div class="tr"><div class="tc"></div></div></div>')
      .append('<div class="ml"><div class="mr"><div class="mc"></div></div></div>')
      .append('<div class="bl"><div class="br"><div class="bc"></div></div></div>'))
    .width(config.w || 250)
    .css({ 
      position : 'absolute',
      opacity : .1,
      zIndex : Gadget.zIndex++,
      left : typeof config.x == 'number' ?
             config.x : Math.floor((dashboard.el.width() - (config.w || 250)) / 2),
      top :  typeof config.y == 'number' ?
             config.y : Math.floor((dashboard.el.height() - (config.h || 150)) / 2)
    })
    .appendTo(dashboard.currentScreen())
    .draggable({ containment : '.dashboard' })
    .resizable({ 
      minWidth : 150,
      minHeight : 100,
      handles : 'n,e,w,s,ne,sw,se', 
      alsoResize : '#'+id+' .mc' 
    })
    .dblclick(function() {
      gadget.toggleGrow()
    })
    .mousedown(function() { 
      dashboard.dirty(true);
      gadget.focus(true);
    })
    .mouseup(function() {
      gadget.uncoverContent();
    })
    .bind('dragstart', function() { 
      gadget.coverContent();
    })
    .bind('dragstop', function() {
      gadget.uncoverContent();
    })
    .bind('dragstop', function() { dashboard.dirty(true) })
    .bind('resizestop', function() { 
      gadget.savedHeight = $(this).height();
      gadget.el.find('.mc iframe').height(gadget.el.find('.mc').height());
      dashboard.dirty(true);
    })
    .data('gadget', this)

  var mc = gadget.el.find('.mc');
  $('<div class="body"></div>')
    .appendTo(mc)
    .gadget(config.url, gadget.params(), { 
      prefs : gadget.initConfig.prefs,
      presentation : gadget.initConfig.view
    });

  mc.mousedown(function(e) { e.stopPropagation() })
  if (typeof config.h == 'number') {
    setTimeout(function() { mc.find('iframe').height(mc.height()) }, 1000)
  }

  $(['<div class="pref">',
     '<table>',
     '<tr><td class="p">'+Gadget.LABEL_TITLE+'</td>',
     '    <td class="v title"><input type="text" name="title" /></td></tr>',
     '<tr><td class="p">'+Gadget.LABEL_COLOR+'</td>',
     '    <td class="v color"></td>',
     '</table>',
     '<div class="buttons">',
     '<input type="button" class="cancel" value="'+Gadget.LABEL_BUTTON_CANCEL+'" />',
     '<input type="button" class="ok" value="'+Gadget.LABEL_BUTTON_OK+'" />',
     '</div>',
     '</div>' ].join(''))
    .find('table tr td.color').each(function() {
      var td = $(this);
      $.each(Gadget.COLORS, function(i, c) {
        td.append(
          $('<span class="color" />')
            .data('color', c)
            .addClass(c)
            .addClass(config.color==c ? 'selected' : '')
            .click(function() {
              td.find('.color').each(function() {
                var sp = $(this);
                sp[sp.hasClass(c) ? 'addClass' : 'removeClass']('selected')
              })
            })
        )
      })
    }).end()
    .find('table').each(function() {
      var table = $(this);
      $.each(config.userPrefs || [], function(i, pref) {
        var tr = $('<tr class="pref" />')
          .append($('<td class="p" />').text(pref.displayName || ""));
        if (pref.dataType!=="enum") {
          var inputType = 
            pref.dataType == 'hidden' ? 'hidden' : 
            pref.dataType == 'string' ? 'text' :
            pref.dataType == 'bool' ? 'check' : 'text';
          tr.append(
            $('<td class="v" />').append(
              $('<input />')
                .attr('type', inputType)
                .attr('name', pref.name)
                .val(pref.defaultValue || '')
            )
          )
        } else {
          var select = $('<select />').attr('name', pref.name);
          $.each(pref.enumValues || [], function() {
            select.append($('<option />')
              .val(this.value)
              .text(this.displayValue || this.value));
          })
          tr.append(
            $('<td class="v" />').append(select)
          )
        }
        table.append(tr);
      })
    }).end()
    .find('.buttons > .cancel').click(function() {
      mc.children('.body, .pref').toggle();
    }).end()
    .find('.buttons > .ok').click(function() {
      gadget.savePreference();
      gadget.el.find('.body')
        .gadget(gadget.initConfig.url, gadget.params(), { 
          prefs : gadget.initConfig.prefs,
          presentation : gadget.initConfig.view
        });
      dashboard.dirty(true);
      mc.children('.body, .pref').toggle();
    }).end()
    .hide()
    .appendTo(mc)

  if (config.title) {
    $('<div class="header">')
      .appendTo(gadget.el.find('.tc'))
      .text(config.title)
  }
  if (typeof config.h == 'number') {
    gadget.height(config.h);
  }
  if (config.closable !== false) {
    $('<div class="btn-close-wrap" />')
      .append($('<div class="btn-close" />').hide())
      .mouseover(function() { $(this).children().show() })
      .mouseout(function() { $(this).children().hide() })
      .click(function() {
        dashboard.dirty(true);
        gadget.el.fadeOut('slow', function() { $(this).remove() });
      })
      .appendTo(gadget.el)
  }
  $('<div class="btn-pref-wrap" />')
    .append($('<div class="btn-pref-ctrl" />')
      .mouseover(function() { 
        $(this.parentNode).children('.btn-pref').show();
      })
      .mouseout(function() { 
        $(this.parentNode).children('.btn-pref').hide();
      })
      .click(function() {
        gadget.loadPreference();
        gadget.el.find('.mc').children('.body, .pref').toggle();
      })
    )
    .append($('<div class="btn-pref" />').hide())
    .appendTo(gadget.el)

  gadget.el.animate({ opacity : .9 }, { duration : 1000, complete : function() {
    if (focus) { gadget.focus() }
  }});
}

Gadget.count = 0;
Gadget.zIndex = 1000;

Gadget.COLORS = 'blue,cyan,green,yellow,red,magenta,gray,white'.split(',');
Gadget.LABEL_TITLE = 'Title';
Gadget.LABEL_COLOR = 'Color';
Gadget.LABEL_BUTTON_CANCEL = 'Cancel';
Gadget.LABEL_BUTTON_OK = 'OK';

Gadget.prototype = {
  /**
   *
   */
  skin : { CONTENT_BG_COLOR : '#ffffff' },

  /**
   *
   */
  loadPreference : function() {
    var gadget = this;
    this.el.find('.pref')
      .find('td.title input').val(this.initConfig.title || '').end()
      .find('td.color span.color').removeClass('selected').end()
      .find('td.color span.'+(this.initConfig.color || 'blue')).addClass('selected').end()
      .find('tr.pref input, tr.pref select').each(function() {
        this.value = gadget.pref(this.name) || '';
      });
  }
  ,

  /**
   *
   */
  savePreference : function() {
    var gadget = this;
    var pref = this.el.find('.pref')
    var title = this.initConfig.title = pref.find('td.title input').val();
    var color = this.initConfig.color = pref.find('td.color span.selected').data('color');
    var main = this.el.find('> .main');
    $.each(Gadget.COLORS, function(i, c) {
      if (c !== color) main.removeClass(c);
      else main.addClass(c);
    });
    pref.find('tr.pref input, tr.pref select').each(function() {
      gadget.pref(this.name, this.value);
    });
    this.title(title);
  }
  ,

  /**
   *
   */
  title : function(title) {
    if (typeof title === 'string') {
      this.el.find('.header').text(title);
      return this;
    } else {
      return this.el.find('.header').text();
    }
  }
  ,

  /**
   *
   */
  pref : function(name, value) {
    var prefs;
    this.initConfig.prefs = this.initConfig.prefs || {};
    if (typeof name == 'string') {
      if (typeof value == 'undefined') {
        return this.initConfig.prefs[name]
      } else {
        prefs = {};
        prefs[name] = value;
      }
    } else if (typeof name == 'undefined') {
      return this.initConfig.prefs;
    } else {
      prefs = name;
    }
    this.initConfig.prefs = $.extend(this.initConfig.prefs, prefs);
    return this;
  }
  ,

  /**
   *
   */
  focus : function(cover) {
    var id = this.el.attr('id');
    $('.floating-gadget').each(function() {
      var gadget = $(this).data('gadget');
      if (gadget.el.attr('id')===id) {
        gadget.el.css({ opacity : 1, zIndex : Gadget.zIndex++ });
        if (!cover) gadget.uncoverContent();
      } else {
        gadget.el.css({ opacity : .8 });
        gadget.coverContent();
      }
    })
  }
  ,

  /**
   *
   */
  params : function() {
    var params = $.extend({ skin : this.skin }, environment );
    if (params.selectedText && params.selectedText.length>50) {
      params.selectedText = params.selectedText.substring(0, 50);
    }
    return params;
  }
  ,

  /**
   *
   */
  config : function() {
    var pos = this.el.position();
    var config = $.extend({}, this.initConfig);
    var h = this.el.css('height');
    if (/^[\d]+px$/.test(h)) {
      config.h = this.el.height();
    } else {
      delete config.h;
    }
    config = $.extend(config, {
      x : pos.left, y : pos.top,
      w : this.el.width()
    });
    return config;
  }
  ,

  /**
   *
   */
  height : function(height) {
    if (height==='auto') {
      this.el.height('auto');
      this.el.find('.mc').width('auto').height('auto').end();
      var screenH = $(window).height();
      if (this.el.height() > screenH) {
        var sh = this.savedHeight;
        this.height(screenH);
        this.savedHeight = sh;
        this.el.css('top', '0px')
               .find('.mc iframe').height(this.el.find('.mc').height());
      }
    } else if (typeof height=='number') {
      var diff = this.el.height() - height;
      var mc = this.el.find('.mc');
      mc.height(mc.height() - diff);
      this.el.height(height)
      this.savedHeight = height;
    }
  }
  ,

  /**
   *
   */
  toggleGrow : function() {
    if (typeof this.savedHeight == 'number') {
      var height = this.el.height();
      this.height(height == this.savedHeight ? 'auto' : this.savedHeight);
    } else {
      this.height('auto')
    }
  }
  ,

  /**
   *
   */
  coverContent : function() {
    var main = this.el.find('.main');
    main.prepend(
      $('<div class="cover" />')
        .width(main.width()).height(main.height())
        .html('&#160;')
    );
  }
  ,

  /**
   *
   */
  uncoverContent : function() {
    this.el.find('.cover').remove();
  }
  ,

  /**
   *
   */
  handleResize : function() {
  }
}

/**
 *
 */
var GadgetBar = function(gadgets, container) { 
  var gbar = this;

  var defaultGadgets = {};
  for (var url in GadgetBar.DEFAULT_GADGETS) {
    defaultGadgets[url] = $.extend({ 'default' : true }, GadgetBar.DEFAULT_GADGETS[url])
  }
  gadgets = $.extend(defaultGadgets, gadgets);

  gbar.el = $('<div class="gadget-bar"></div>')
    .append(
      $('<div class="buttons closed"><div class="btn-toggle"></div></div>')
        .click(function() {
          $(this).toggleClass('closed')
            .find('> .btn-toggle').remove().end()
            .append('<div class="btn-toggle"></div>')
            .parent().find('> .main').slideToggle()
                     .children().toggle(100)
        })
    )
    .append(
      $('<div class="main"></div>').append(
        $('<div class="gadget-list-wrapper" />').append(
          $('<div class="gadget-list" />')
            .append('<div class="gadget-container" />')
            .append('<div class="gadget-control prev" />')
            .append('<div class="gadget-control next" />')
            .find('.gadget-control').click(function() {
              var container = $(this).parent().children('.gadget-container');
              var top = container.position().top;
              var height = container.height();
              var line = -Math.round(top/80);
              var max = Math.round(height/80);
              if ($(this).hasClass('next')) {
                container.css({ top : -((line+1) % max)*80 });
              } else if ($(this).hasClass('prev')) {
                container.css({ top : -((line-1+max) % max)*80 });
              }
            }).end()
          )
          .hide()
        )
        .append($('<div class="ext-list"></div>').hide())
        .hide()
    )
    .appendTo(container);

  gbar.el.find('.ext-list')
    .append(
      $('<div class="gadget new-gadget"></div>')
        .append($('<div class="title"></div>').text(GadgetBar.LABEL_LOAD_NEW_GADGET))
        .append($('<div class="icon"></div>'))
        .click(function() { gbar.showNewGadgetDialog() })
    )
    .append(
      $('<div class="gadget trash"></div>')
        .append($('<div class="title"></div>').text(GadgetBar.LABEL_TRASH_GADGET))
        .append($('<div class="icon"></div>'))
        .append($('<div class="cloud"></div>').hide())
        .droppable({
          scope : 'gadget',
          hoverClass : 'trash-hover',
          drop : function(e, ui) {
            ui.draggable.remove();
            var cloud = $(this).find('.cloud');
            cloud.show();
            setTimeout(function() { 
              cloud.hide(); 
              gbar.disableTrash();
              gbar.save();
            }, 500)
          }
        })
        .hide()
    );

  for (var url in gadgets) {
    var gdconfig = $.extend({ url : url }, gadgets[url]);
    this.addGadget(gdconfig);
  }

}

GadgetBar.LABEL_LOAD_NEW_GADGET = 'Add Gadget';
GadgetBar.LABEL_TRASH_GADGET = 'Remove Gadget';
GadgetBar.DEFAULT_GADGETS = {
  'http://www.google.com/friendconnect/gadgets/members.xml' : {
    title : 'Member',
    thumbnail : 'http://www.google.com/friendconnect/scs/images/thumb/members.png'
  },
  'http://www.google.com/friendconnect/gadgets/wall.xml' : {
    title : 'Comment',
    thumbnail : 'http://www.google.com/friendconnect/scs/images/thumb/wall.png'
  }
};

GadgetBar.prototype = {
  /**
   *
   */
  registerDashboard : function(dashboard) {
    this.dashboard = dashboard;
  }
  ,

  /**
   *
   */
  showNewGadgetDialog : function() {
    var url = prompt('input new gadget URL', '')
    if (/^https?:\/\//.test(url)) {
      this.loadGadget(url);
    }
  }
  ,

  /**
   *
   */
  loadGadget : function(url) {
    var gbar = this;

    $.ajax({
      url : url,
      dataType : 'xml'
    }).next(function(doc) {
      findTranslater(doc, function(translate) {
        var modulePref = $('ModulePrefs', doc);
        var gdconfig = { 
          title : translate(modulePref.attr('title')),
          description : translate(modulePref.attr('description')),
          thumbnail : toAbsoluteUrl(translate(modulePref.attr('thumbnail')), url),
          userPrefs : getUserPrefs(doc, translate)
        };
        var v;
        var views = getContentViews(doc);
        for (var i=0; i<views.length; i++) {
          if (views[i]=="profile") {
            v = "profile"; break;
          } else if (views[i]=="canvas") {
            v = "canvas";
          }
        }
        gdconfig.view = v;

        gbar.addGadget($.extend({ url : url }, gdconfig));
        gbar.save();

      });
    }).error(function() {
      //console.log(arguments);
    });

    /**
     *
     */
    function findTranslater(doc, callback) {
      var l = (navigator.language || navigator.userLanguage).split(/[\-_]/);
      var lang = l[0] && l[0].toLowerCase();
      var country = l[1] && l[1].toLowerCase();

      // scan locale elements
      var resUrl;
      $('ModulePrefs > Locale', doc).each(function() {
        var locale = $(this);
        var llang = (locale.attr('lang') || '').toLowerCase();
        var lctry = (locale.attr('country') || '').toLowerCase();
        if ((llang === '' && lctry === '') ||
            (llang === lang && (lctry === '' || lctry === country))) {
          resUrl = locale.attr('messages');
        }
      });

      // fetch translate resource message
      if (!resUrl) {
        callback(function(s) { return s });
      } else {
        resUrl = toAbsoluteUrl(resUrl, url);
        $.ajax({
          url : resUrl,
          dataType : 'xml'
        }).next(function(doc) {
          var trmaps = {};
          var names = [];
          $('messagebundle > msg', doc).each(function() {
            var name = $(this).attr('name');
            var text = $(this).text().replace(/^\s*|\s*$/,'');
            if (!name) return;
            trmaps[name] = text;
            names.push(name);
          });
          var regexp = new RegExp('__MSG_('+names.join('|')+')__', 'g');
          callback(function(s) {
            return (s || '').replace(regexp, function($0, $1) { return trmaps[$1] });
          });
        });
      }
    }

    /**
     *
     */
    function toAbsoluteUrl(url, baseUrl) {
      if (url && !/^https?:\/\//.test(url)) {
        baseUrl = url.indexOf('/')==0 ? 
          baseUrl.split('/').slice(0, 3).join('/') :
          baseUrl.split('/').slice(0, -1).join('/')+'/';
        url = baseUrl + url;
      }
      return url;
    }

    /**
     *
     */
    function getUserPrefs(doc, translate) {
      return $('UserPref', doc).map(function() {
        var userPref = $(this);
        var pref = {
          name : translate(userPref.attr('name')),
          displayName : translate(userPref.attr('display_name')),
          dataType : userPref.attr('datatype'),
          required : userPref.attr('required')==='true',
          defaultValue : translate(userPref.attr('default_value'))
        }
        if (pref.dataType=='enum') {
          pref.enumValues = [];
          userPref.find('EnumValue').each(function() {
            var enumValue = $(this);
            pref.enumValues.push({
              value : translate(enumValue.attr('value')),
              displayValue : translate(enumValue.attr('display_value'))
            });
          });
        }
        return pref;
      }).get();
    }

    /**
     *
     */
    function getContentViews(doc) {
      var views = [];
      $('Module > Content', doc).each(function() {
        var v = $(this).attr('view');
        if (v) Array.prototype.push.apply(views, v.split(/\s*,\s*/));
      })
      return views;
    }
  }
  ,

  /**
   *
   */
  addGadget : function(gdconfig) {
    var gbar = this;
    var gadget = $('<div class="gadget"></div>')
      .append($('<div class="title"></div>').text(gdconfig.title))
      .append($('<div class="icon"></div>'))
      .data('config', gdconfig)
      .draggable({ 
        scope : 'gadget',
        opacity : .5,
        zIndex : 40000,
        revert : 'invalid', 
        helper : 'clone',
        appendTo : document.body 
      })
      .click(function() { gbar.dashboard.addGadget($(this).data('config'), true) })
      .bind('dragstart', function() { if (!gdconfig['default']) gbar.enableTrash() })
      .bind('dragstop', function() { gbar.disableTrash() })
      .appendTo(gbar.el.find('.gadget-container'))
    if (gdconfig.thumbnail) {
      gadget.find('.icon').append($('<img />').attr('src', gdconfig.thumbnail))
    } else {
      gadget.addClass('default');
    }
    return this;
  }
  ,

  /**
   *
   */
  enableTrash : function() {
    this.el.find('.ext-list')
      .children('.trash').show().end()
      .children('.new-gadget').hide().end()
  }
  ,

  /**
   *
   */
  disableTrash : function() {
    this.el.find('.ext-list')
      .children('.trash').hide().end()
      .children('.new-gadget').show().end()
  }
  ,

  /**
   *
   */
  save : function() {
    var gadgets = {};
    this.el.find('.gadget-container .gadget').each(function() {
      var gdconfig = $(this).data('config')
      if (!GadgetBar.DEFAULT_GADGETS[gdconfig.url]) {
        gadgets[gdconfig.url] = $.extend({}, gdconfig);
        delete gadgets[gdconfig.url].url;
      }
    });
    $.ajax({
      type: 'post',
      url: '/appdata/@viewer/@self',
      data: {
        gadgets : gadgets
      },
      dataType: 'data'
    });
    return this;
  }

}


/**
 *
 */
var Dialog = function(config) {
  this.config = config || {};
} 

Dialog.prototype = {
  /**
   *
   */
  show : function() {
    var dialog = this;
    var body = $(document.body);
    var docW = $(document).width(), docH = $(document).height();
    var winH = $(window).height();
    this.mask = $('<div class="dialog-mask" />')
      .width(docW).height(docH)
      .appendTo(body)
    var w = this.config.width || 400;
    var h = this.config.height || 300;
    this.el = $('<div class="dialog-content" />')
      .width(w).height(h)
      .css({ left : Math.floor((docW - w) *.5) + 'px', 
             top : Math.floor((winH - h) * .5) + 'px' })
      .appendTo(body)

    this.render();
  }
  ,

  /**
   *
   */
  close : function() {
    if (this.mask) this.mask.remove();
    if (this.el) this.el.remove();
  }

}

/**
 *
 */
var SettingDialog = extend(Dialog, {
  /**
   *
   */
  init : function(config) {
    this.dashboard = config.dashboard;
  }
  ,

  /**
   *
   */
  render : function() {
    var dialog = this;

    this.el.append([
      '<div class="body">',
      ' <h1>'+SettingDialog.LABEL_DASHBOARD_PROPS+'</h1>',
      ' <div class="prompt">',
      '   <span class="label">'+SettingDialog.LABEL_BGCOLOR+':</span>',
      '   <input style="width:60px" name="bgcolor" type="text" maxlength="7" />',
      ' </div>',
      ' <div class="prompt">',
      '   <span class="label">'+SettingDialog.LABEL_BG_IMAGE_URL+':</span><br/>',
      '   <input class="url" name="bgimage" type="text" />',
      ' </div>',
      ' <div class="prompt">',
      '   <span class="label">'+SettingDialog.LABEL_OPACITY+':</span>',
      '   <input style="width:30px" name="opacity" type="text" maxlength="6" />',
      ' </div>',
      ' <div class="prompt">',
      '   <span class="label">'+SettingDialog.LABEL_URL_PATTERN+':</span><br/>',
      '   <input class="url" name="urlpattern" type="text" />',
      ' </div>',
      '</div>'
    ].join(''))
    .append([
      '<div class="buttons">',
      '  <input type="button" name="cancel" value="'+SettingDialog.LABEL_BUTTON_CANCEL+'" />',
      '  <input type="button" name="ok" value="'+SettingDialog.LABEL_BUTTON_OK+'" />',
      '</div>'
    ].join(''))
    .find('.buttons')
      .children('input[name="cancel"]').click(function() {
        dialog.close();
      }).end()
      .children('input[name="ok"]').click(function() {
        dialog.save();
      })
    .end()

    var scrconfig = this.dashboard.config.screens[this.dashboard.targetIdx];
    var body = this.el.find('.body');
    body.find('input[name="bgcolor"]').val(scrconfig.bgcolor);
    body.find('input[name="bgimage"]').val(scrconfig.bgimage);
    body.find('input[name="opacity"]').val(scrconfig.opacity ? Math.floor(scrconfig.opacity * 100) : '');
    body.find('input[name="urlpattern"]').val(scrconfig.urlpattern);
  }
  ,

  /**
   *
   */
  save : function() {
    var body = this.el.find('.body');
    var bgcolor = body.find('input[name="bgcolor"]').val();
    if (bgcolor && !/^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(bgcolor)) {
      alert(SettingDialog.MESSAGE_BGCOLOR_FORMAT);
      return;
    }
    var bgimage = body.find('input[name="bgimage"]').val();
    if (bgimage && !/^https?:\/\//.test(bgimage)) {
      alert(SettingDialog.MESSAGE_BGIMAGE_FORMAT);
      return;
    }
    var opacity = body.find('input[name="opacity"]').val();
    if (opacity) {
      opacity = parseInt(opacity, 10);
      if (opacity<0 || opacity > 100) {
        alert(SettingDialog.MESSAGE_OPACITY_FORMAT);
        return;
      }
    }
    var urlpattern = body.find('input[name="urlpattern"]').val();
    if (urlpattern && !/^https?:\/\//.test(urlpattern)) {
      alert(SettingDialog.MESSAGE_URLPATTERN_FORMAT);
      return;
    }

    var scrconfig = this.dashboard.config.screens[this.dashboard.targetIdx];
    if (bgcolor) scrconfig.bgcolor = bgcolor; else delete scrconfig.bgcolor;
    if (bgimage) scrconfig.bgimage = bgimage; else delete scrconfig.bgimage;
    if (opacity) scrconfig.opacity = opacity/100; else delete scrconfig.opacity;
    if (urlpattern) scrconfig.urlpattern = urlpattern; else delete scrconfig.urlpattern;

    this.dashboard.save();

    if (bgcolor || bgimage || opacity) {
      this.dashboard.refreshBackground();
    }

    this.close();
  }

});

SettingDialog.LABEL_DASHBOARD_PROPS = 'Dashboard Setting';
SettingDialog.LABEL_BGCOLOR = 'Background Color';
SettingDialog.LABEL_BG_IMAGE_URL = 'Background Image URL';
SettingDialog.LABEL_OPACITY = 'Background Opacity (0 - 100)';
SettingDialog.LABEL_URL_PATTERN = 
  'URL Pattern (for bookmarklet; "*" is wildcard)';
SettingDialog.LABEL_BUTTON_CANCEL = 'Cancel';
SettingDialog.LABEL_BUTTON_OK = 'OK';
SettingDialog.MESSAGE_BGCOLOR_FORMAT = 'Background Color format should be #fff or #ffffff';
SettingDialog.MESSAGE_BGIMAGE_FORMAT = 'Background Image URL is not valid';
SettingDialog.MESSAGE_OPACITY_FORMAT = 'Background Opacity should be number from 0 to 100';
SettingDialog.MESSAGE_URLPATTERN_FORMAT = 'URL Pattern is not valid';



/**
 *
 */
var plugins = [];
var environment = {};
var preferences = {};

/**
 *
 */
return {

  Dashboard : Dashboard,
  Gadget : Gadget,
  GadgetBar : GadgetBar,
  Dialog : Dialog,
  SettingDialog : SettingDialog,

  /**
   *
   */
  DEFAULT_DASHBOARD : {
    screens : [{
      name : 'default',
      bgcolor : '#667788',
      opacity : .5
    }, {
      name : 'work',
      bgcolor : '#aaffaa',
      opacity : .5
    }, {
      name : 'hobby',
      bgcolor : '#ffaaff',
      opacity : .5
    }]
  }
  ,

  /**
   *
   */
  registerPlugin : function(plugin) {
    plugins.push(plugin);
  }
  ,

  /**
   *
   */
  init : function() {
    var overlay = location.href.split('#').pop()=='overlay';
    $(document.body)
      .addClass(overlay ? 'overlay' : 'standalone')
      .addClass($.browser.msie ? "msie" : "");
    environment = $.extend(environment, window.name ? gadgets.json.parse(window.name) : {});
    SocialDashboard.initPlugins();
  }
  ,

  /**
   *
   */
  initPlugins : function() {
    var count = 0;
    if (plugins.length>0) {
      $.each(plugins, function(i, plugin) {
        plugin.execute(environment, next, next)
      })
    } else {
      SocialDashboard.loadConfig();
    }
    function next() { 
      count++; 
      if (count==plugins.length) SocialDashboard.loadConfig();
    }
  }
  ,

  /**
   *
   */
  loadConfig : function() {
    $.ajax({ 
      url : '/appdata/@viewer/@self',
      dataType : 'data'
    })
    .next(function(data) {
      var config;
      for (var vid in data) {
        config = data[vid];
        break;
      }
      if (!config) {
        config = {
          dashboard : deepCopy(SocialDashboard.DEFAULT_DASHBOARD)
        }
      }
      SocialDashboard.render(config);
    })
    .error(function(error) {
      var config = {
        dashboard : deepCopy(SocialDashboard.DEFAULT_DASHBOARD)
      }
      SocialDashboard.render(config);
    });
  }
  ,

  /**
   *
   */
  render : function(config) {
    $(document.body).children('.root').remove();
    var root = $('<div class="root" />').appendTo(document.body);

    if (!config.dashboard) {
      config.dashboad = deepCopy(SocialDashboard.DEFAULT_DASHBOARD);
    }
    if (!config.dashboard.screens) {
      var screen = $.extend({}, SocialDashboard.DEFAULT_DASHBOARD.screens[0] || {});
      delete screen.gadgets;
      screen = $.extend(config.dashboard, screen);
      config.dashboard = {
        screens : [ screen ].concat(
          SocialDashboard.DEFAULT_DASHBOARD.screens.slice(1))
      }
    }
    var gbar = new GadgetBar(config.gadgets || [], root);
    var dashboard = new Dashboard(config.dashboard, root);
    gbar.registerDashboard(dashboard);

    // auto saving in 10 sec if dirty...
    setInterval(function() {
      if (dashboard.dirty()) dashboard.save();
    }, 10000);
  }
  ,

  /**
   *
   */
  refresh : function() {
    $(document.body).children('.root').remove();
    this.loadConfig();
  }
  ,


  /**
   *
   */
  resetConfig : function(keys) {
    keys = keys || ['dashboard','dashboards','gadgets'];
    var req = opensocial.newDataRequest();
    req.add(req.newRemovePersonAppDataRequest(
      opensocial.IdSpec.PersonId.VIEWER, keys), "response")
    req.send(function() {})
  }
  ,

  /**
   *
   */
  gadgetUrl : function(name) {
    return this.dashboardBaseUrl() + '/gadgets/' + name + '.xml';
  }
  ,

  /**
   *
   */
  dashboardBaseUrl : function() {
    if (this._dashboardBaseUrl) return this._dashboardBaseUrl;

    var scripts = document.getElementsByTagName('script');
    var src = scripts[scripts.length-1].src;
    if (!/^https?:\/\//.test(src)) { // ie returns relative path
      if (src.indexOf('/')==0) {
        src = location.href.split('/').slice(0, 3).join('/') + src;
      } else {
        src = location.href.split('/').slice(0, -1).join('/') + '/' + src;
      }
    }
    return this._dashboardBaseUrl = src.split('/').slice(0, -2).join('/');
  }
  ,

  /**
   *
   */
  fixpng : window.ActiveXObject ? // IE
    function(el) {
      var src = el.currentStyle.getAttribute("backgroundImage").slice(5,-2);
      el.style.backgroundImage = 'url('+src.replace(/png$/, 'gif')+')';
      el.style.behavior = "none";
    } : function() {}
  
}

}
